#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <netdb.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "event.h"
#include "rudp.h"
#include "rudp_api.h"

//This will safely allocate memory for a variable
#define smalloc(type,name) type* name = (type*)malloc(sizeof(type)); if (name==NULL) { perror("malloc: name"); return -1; }

//Defining socket states
#define INITIAL     0
#define SENT_SYN    1
#define SENDING     2
#define RECEIVING   3
#define SENT_FIN    4
#define CLOSED      5

//Internal structure to handle all information about a socket
struct rudp_socket_int {
    int socket_desc;                //file descriptor to handle socket
    struct sockaddr remote_addr;    //address of remote host
    struct sockaddr_in local_addr;  //local address
    int socket_id;                  //number of socket
};

//Structure to pass socket and handler info to eventf1 and recvf1
struct rudp_socket_event {
    struct rudp_socket_int* sockint;
    rudp_event_t fevent;
};

//Single RUDP packet body
struct rudp_packet_body {
    char* data;
    int len;
};

//Entire RUDP packet
struct entire_rudp_packet {
    struct rudp_hdr rh;
    struct rudp_packet_body rpb;
};

struct timeout_arg {
    int sock_id;
    int socket_desc;
    u_int32_t seqno;
    int num;
    struct sockaddr_in* to;
    //struct packet_queue* pqp;
};

//Structure for the packet queue of a socket/peer pair
struct packet_queue {
    struct packet_queue* prev;
    struct rudp_packet_body rp;
    int num;
    int timed_out;
    int syn_timed_out;
    int fin_timed_out;
    u_int32_t seqno;
    struct timeout_arg* tap;    //timeout argument in case of DATA timeout
    struct packet_queue* next;
};

//Global structure to manage socket/peer state
struct socket_state {
    int num_of_peers;               //number of peers connected to this socket
    u_int32_t syn_seqno[32];        //SYN sequence numbers for each peer
    struct timeout_arg* stap[32];   //timeout argument in case of SYN timeout
    u_int32_t fin_seqno[32];        //SYN sequence numbers for each peer
    struct timeout_arg* ftap[32];   //timeout argument in case of FIN timeout
    int syn_timed_out[32];
    int fin_timed_out[32];
    struct sockaddr_in* peers[32];  //remote peer addresses are kept here
    int peer_state[32];             //keep track of state for each peer
    int need_to_close;              //flag set by rudp_close()

    int first_packet[32];           //the number of the first packet in the queue
    int last_packet[32];            //the number of the last packet in the queue
    int window_size[32];            //current size of the sliding window

    struct packet_queue* curr[32];  //points to current packet for each peer

    int (*udp_recv_fh)(rudp_socket_t,struct sockaddr_in*,char*,int);    //data input function handler
    int (*udp_event_fh)(rudp_socket_t,rudp_event_t,struct sockaddr_in*);   //event function handler
};

int num_of_sockets = 0;             //total number of sockets
struct socket_state state[20];      //State for each socket

//This is an alias for shortness
#define sid sockint->socket_id

void init_queue(int sock_id,int pnum){

    //Initialize a queue for the socket; packet 0 is empty
    state[sock_id].curr[pnum] = (struct packet_queue*)malloc(sizeof(struct packet_queue));
    state[sock_id].curr[pnum]->next = NULL;
    state[sock_id].curr[pnum]->prev = NULL;

    state[sock_id].curr[pnum]->num = 0;
    state[sock_id].curr[pnum]->rp.len = 0;
    state[sock_id].curr[pnum]->rp.data = NULL;

    //Generate the starting sequence number
    srand(1);
    state[sock_id].curr[pnum]->seqno = (rand() % 10000);

    //Last and first packets are 0
    state[sock_id].first_packet[pnum] = 0;
    state[sock_id].last_packet[pnum] = 0;
}

//Adds a packet to a queue
int add_packet(void* data, int len, int sock_id, int pnum)
{
    //We are now at packet cnum
    int cnum = state[sock_id].curr[pnum]->num;
    //Get the current sequence number
    int cseqno = state[sock_id].curr[pnum]->seqno;

    //Move to the last packet 
    int i;
    int shift = (state[sock_id].last_packet[pnum]-cnum);

    for (i=0; i<shift; i++)
    {
        state[sock_id].curr[pnum] = state[sock_id].curr[pnum]->next;
        cseqno++;
    }
        //curr now points to the last packet in queue

    //Create a new RUDP packet and copy the data into it
    smalloc(struct packet_queue,npq);
 
    npq->rp.data = (char*)malloc(len*sizeof(char));
    if (npq->rp.data==NULL) return -1;

    memcpy(npq->rp.data,data,len);
    npq->rp.len = len;
    npq->num = (state[sock_id].curr[pnum]->num)+1;
    npq->timed_out = 0;
    npq->seqno = cseqno+1;

    //Initialize the next member of the queue
    state[sock_id].curr[pnum]->next = (struct packet_queue*)malloc(sizeof(struct packet_queue));
    memcpy(state[sock_id].curr[pnum]->next,npq,sizeof(struct packet_queue));
    //Set its prev to point to the previous packet
    state[sock_id].curr[pnum]->next->prev = state[sock_id].curr[pnum];

    //Now, get the pointer to the current packet back
    for (i=0; i<shift; i++)
        state[sock_id].curr[pnum] = state[sock_id].curr[pnum]->prev;

    //Increase the packet count in the queue
    state[sock_id].last_packet[pnum]++;
}

int send_ack(int socket_desc, int sock_id, struct sockaddr_in* to,u_int32_t seqno)
{
    //Send back an ACK
    smalloc(struct rudp_hdr,rh);

    rh->seqno=htonl(seqno);
    rh->type = htons(RUDP_ACK);
    rh->version = htons(RUDP_VERSION);

    int nbytes = sizeof(struct rudp_hdr);
    size_t ssize = sizeof(struct sockaddr);

    if (sendto(socket_desc,(void*)rh,nbytes,0,(struct sockaddr*)to,ssize)==-1)
        return -1;

    fprintf(stderr,"Send an ACK with seqno %d\r\n",htonl(rh->seqno));

    return 0;
}

int udp_timeoutf(int ia, void* va)
{
    //Get the packet which was timed out
    struct timeout_arg* targ = (struct timeout_arg*)va;
    fprintf(stderr,"Timeout for packet with seqno %d\r\n",targ->seqno);

    //Deregister timeout function
    event_timeout_delete(udp_timeoutf,va);

    //Find which packet we have to retransmit
    int pnum = getpeernum(targ->sock_id,targ->to);
    int shift = (state[targ->sock_id].curr[pnum]->seqno)-(targ->seqno);

    int i;
    for (i=0;i<shift;i++)
    {
        //Move left {shift} times
        state[targ->sock_id].curr[pnum]=state[targ->sock_id].curr[pnum]->prev;
    }

    //Create a packet for retransmission
    smalloc(struct entire_rudp_packet, erp);
    erp->rh.seqno = htonl(targ->seqno);
    erp->rh.type = htons(RUDP_DATA);
    erp->rh.version = htons(RUDP_VERSION);

    erp->rpb.len = state[targ->sock_id].curr[pnum]->rp.len;
    erp->rpb.data = (char*)malloc(erp->rpb.len);
    memcpy(erp->rpb.data,state[targ->sock_id].curr[pnum]->rp.data,erp->rpb.len);

    //Actually send packet
    int nbytes = sizeof(struct rudp_hdr)+erp->rpb.len+16;
    size_t size = sizeof(struct sockaddr);

    if (sendto(targ->socket_desc,(void*)erp,nbytes,0,(struct sockaddr*)(targ->to),size)==-1)
    {
        //Return to the current packet
        for (i=0;i<shift;i++)
            state[targ->sock_id].curr[pnum]=state[targ->sock_id].curr[pnum]->next;
        perror("udp_timeoutf: sendto");
        return -1;
    }

    //Register a new timeout for the packet
    smalloc(struct timeval,current_time);
    if (gettimeofday(current_time,NULL)<0)
    {
        //Return to the current packet
        for (i=0;i<shift;i++)
            state[targ->sock_id].curr[pnum]=state[targ->sock_id].curr[pnum]->next;
        perror("udp_timeoutf: gettimeofday");
        return -1;
    }
    current_time->tv_usec += RUDP_TIMEOUT*2000;

    state[targ->sock_id].curr[pnum]->timed_out += 1;
    if (state[targ->sock_id].curr[pnum]->timed_out>RUDP_MAXRETRANS)
    {
        //Return to the current packet
        for (i=0;i<shift;i++)
            state[targ->sock_id].curr[pnum]=state[targ->sock_id].curr[pnum]->next;
        
        //Call the timeout function
        if (state[targ->sock_id].udp_event_fh((rudp_socket_t)targ->socket_desc,RUDP_EVENT_TIMEOUT,targ->to)==-1)
        {
            perror("udp_timeoutf: event handler failed");
            return -1;
        }
        return 0;
    }

    //Actually register a new timeout function
    event_timeout(*current_time,udp_timeoutf,(void*)state[targ->sock_id].curr[pnum]->tap,"packet timeout");

    //Return to the current packet
    for (i=0;i<shift;i++)
        state[targ->sock_id].curr[pnum]=state[targ->sock_id].curr[pnum]->next;

    return 0;
}

int udp_syn_timeoutf(int ia, void* va)
{
    struct timeout_arg* targ = (struct timeout_arg*)va;
    fprintf(stderr,"Timeout for SYN with seqno %d\r\n",targ->seqno);

    //Deregister timeout function
    event_timeout_delete(udp_syn_timeoutf,va);

    int pnum = getpeernum(targ->sock_id,targ->to);

    //Create a packet for retransmission
    smalloc(struct entire_rudp_packet, erp);
    erp->rh.seqno = htonl(targ->seqno);
    erp->rh.type = htons(RUDP_SYN);
    erp->rh.version = htons(RUDP_VERSION);

    erp->rpb.data = NULL;
    erp->rpb.len = 0;

    //Actually send packet
    int nbytes = sizeof(struct rudp_hdr)+erp->rpb.len+16;
    size_t size = sizeof(struct sockaddr);

    if (sendto(targ->socket_desc,(void*)erp,nbytes,0,(struct sockaddr*)(targ->to),size)==-1)
    {
        perror("udp_syn_timeoutf: sendto");
        return -1;
    }

    //Register a new timeout for the packet
    smalloc(struct timeval,current_time);
    if (gettimeofday(current_time,NULL)<0)
    {
        perror("udp_syn_timeoutf: gettimeofday");
        return -1;
    }
    current_time->tv_usec += RUDP_TIMEOUT*2000;

    state[targ->sock_id].syn_timed_out[pnum] += 1;
    if (state[targ->sock_id].syn_timed_out[pnum]>RUDP_MAXRETRANS)
    {
        //Call the timeout function
        if (state[targ->sock_id].udp_event_fh((rudp_socket_t)targ->socket_desc,RUDP_EVENT_TIMEOUT,targ->to)==-1)
        {
            perror("udp_syn_timeoutf: event handler failed");
            return -1;
        }
        return 0;
    }
    //Actually register a new timeout function
    event_timeout(*current_time,udp_syn_timeoutf,(void*)state[targ->sock_id].stap[pnum],"SYN packet timeout");
}

int udp_fin_timeoutf(int ia, void* va)
{
    struct timeout_arg* targ = (struct timeout_arg*)va;
    fprintf(stderr,"Timeout for FIN with seqno %d\r\n",targ->seqno);

    //Deregister timeout function
    event_timeout_delete(udp_syn_timeoutf,va);

    int pnum = getpeernum(targ->sock_id,targ->to);

    //Create a packet for retransmission
    smalloc(struct entire_rudp_packet, erp);
    erp->rh.seqno = htonl(targ->seqno);
    erp->rh.type = htons(RUDP_FIN);
    erp->rh.version = htons(RUDP_VERSION);

    erp->rpb.data = NULL;
    erp->rpb.len = 0;

    //Actually send packet
    int nbytes = sizeof(struct rudp_hdr)+erp->rpb.len+16;
    size_t size = sizeof(struct sockaddr);

    if (sendto(targ->socket_desc,(void*)erp,nbytes,0,(struct sockaddr*)(targ->to),size)==-1)
    {
        perror("udp_fin_timeoutf: sendto");
        return -1;
    }

    //Register a new timeout for the packet
    smalloc(struct timeval,current_time);
    if (gettimeofday(current_time,NULL)<0)
    {
        perror("udp_fin_timeoutf: gettimeofday");
        return -1;
    }
    current_time->tv_usec += RUDP_TIMEOUT*2000;

    state[targ->sock_id].fin_timed_out[pnum] += 1;
    if (state[targ->sock_id].fin_timed_out[pnum]>RUDP_MAXRETRANS)
    {
        //Call the timeout function
        if (state[targ->sock_id].udp_event_fh((rudp_socket_t)targ->socket_desc,RUDP_EVENT_TIMEOUT,targ->to)==-1)
        {
            perror("udp_fin_timeoutf: event handler failed");
            return -1;
        }
        return 0;
    }
    //Actually register a new timeout function
    event_timeout(*current_time,udp_fin_timeoutf,(void*)state[targ->sock_id].ftap[pnum],"FIN packet timeout");
}

int send_fin(int socket_desc, int sock_id, int pnum, struct sockaddr_in* to)
{
    //Create a new rudp header with FIN
    smalloc(struct rudp_hdr,rh);

    rh->version = htons(RUDP_VERSION);
    rh->type = htons(RUDP_FIN);
    rh->seqno = ntohl(state[sock_id].curr[pnum]->seqno+1);
    
    state[sock_id].fin_seqno[pnum] = state[sock_id].curr[pnum]->seqno+1;

    //Send a FIN
    int nbytes = sizeof(struct rudp_hdr);
    size_t size = sizeof(struct sockaddr_in);

    if (sendto(socket_desc,(void*)rh,nbytes,0,(struct sockaddr*)to,size)==-1)
        return -1;

    //Change state of (socket,peer) pair to SENT_FIN
    if (setsstate(sock_id,to,SENT_FIN)==-1)
        return -1;

    fprintf(stderr,"Sent FIN with seqno %d\r\n",ntohl(rh->seqno));

    //Create an argument for the event_timeout function
        smalloc(struct timeval,current_time);
    if (gettimeofday(current_time,NULL)<0)
    {
        perror("rudp_event_handler: gettimeofday");
        return -1;
    }
    current_time->tv_usec += RUDP_TIMEOUT*2000;

    state[sock_id].ftap[pnum] = (struct timeout_arg*)malloc(sizeof(struct timeout_arg));
    state[sock_id].ftap[pnum]->seqno = ntohl(rh->seqno);
    state[sock_id].ftap[pnum]->sock_id = sock_id;
    state[sock_id].ftap[pnum]->socket_desc = socket_desc;
    state[sock_id].fin_timed_out[pnum] = 0;

    state[sock_id].ftap[pnum]->to = (struct sockaddr_in*)malloc(sizeof(struct sockaddr_in));
    memcpy(state[sock_id].ftap[pnum]->to,to,sizeof(struct sockaddr_in*));
    state[sock_id].ftap[pnum]->to->sin_addr = to->sin_addr;

    //Actually register the timeout handlet
    event_timeout(*current_time,udp_fin_timeoutf,(void*)(state[sock_id].ftap[pnum]),"FIN packet timeout");
    return 0;
}

int send_data(int socket_desc, int sock_id, int pnum, struct sockaddr_in* to)
{
    //Find out how many packets we have to send
    int packets_to_send = state[sock_id].last_packet[pnum]-state[sock_id].curr[pnum]->num;
    if (packets_to_send>state[sock_id].window_size[pnum])
        packets_to_send = state[sock_id].window_size[pnum];

    int i;
    for (i=0; i<packets_to_send; i++)
    {
        //Move to next packet in queue
        state[sock_id].curr[pnum] = state[sock_id].curr[pnum]->next;
        //Create a full RUDP packet
        smalloc(struct entire_rudp_packet, erp);

        erp->rh.seqno = htonl(state[sock_id].curr[pnum]->seqno);
        erp->rh.type = htons(RUDP_DATA);
        erp->rh.version = htons(RUDP_VERSION);

        erp->rpb.len = state[sock_id].curr[pnum]->rp.len;        
        erp->rpb.data = (char*)malloc(erp->rpb.len);
        memcpy(erp->rpb.data,state[sock_id].curr[pnum]->rp.data,erp->rpb.len);

        //Actually send packet
        int nbytes = sizeof(struct rudp_hdr)+erp->rpb.len+16;
        size_t size = sizeof(struct sockaddr_in);

        if (sendto(socket_desc,(void*)erp,nbytes,0,(struct sockaddr*)to,size)==-1)
            return -1;

        fprintf(stderr,"Sent a DATA packet with seqno %d\r\n",ntohl(erp->rh.seqno));

        //Decrease window size by 1
        state[sock_id].window_size[pnum] -= 1;

        //Get the time at which the timeout handler for the packet should be called
        smalloc(struct timeval,current_time);
        if (gettimeofday(current_time,NULL)<0)
        {
            perror("rudp_event_handler: gettimeofday");
            return -1;
        }
        current_time->tv_usec += RUDP_TIMEOUT*2000;

        //Create an argument for the event_timeout function
        state[sock_id].curr[pnum]->tap = (struct timeout_arg*)malloc(sizeof(struct timeout_arg));   
        state[sock_id].curr[pnum]->tap->seqno = ntohl(erp->rh.seqno);
        state[sock_id].curr[pnum]->tap->sock_id = sock_id;
        state[sock_id].curr[pnum]->tap->socket_desc = socket_desc;
        state[sock_id].curr[pnum]->timed_out = 0;

        state[sock_id].curr[pnum]->tap->to = (struct sockaddr_in*)malloc(sizeof(struct sockaddr_in));
        memcpy(state[sock_id].curr[pnum]->tap->to,to,sizeof(struct sockaddr_in*));
        state[sock_id].curr[pnum]->tap->to->sin_addr = to->sin_addr;

        //Actually register the timeout handlet
        event_timeout(*current_time,udp_timeoutf,(void*)(state[sock_id].curr[pnum]->tap),"packet timeout");
    }

    //Send a FIN if no data was left
    if (packets_to_send<1) send_fin(socket_desc,sock_id,pnum,to);
    return 0;
}

int send_syn(int socket_desc, int sock_id, int pnum, struct sockaddr_in* to)
{
    //Create a new rudp header with SYN
    smalloc(struct rudp_hdr,rh);

    rh->version = htons(RUDP_VERSION);
    rh->type = htons(RUDP_SYN);

    int cnum = state[sock_id].curr[pnum]->seqno;
    int shift = state[sock_id].curr[pnum]->num;

    rh->seqno = ntohl(cnum - shift);
    state[sock_id].syn_seqno[pnum] = cnum - shift;;

    //Send a SYN
    int nbytes = sizeof(struct rudp_hdr);
    size_t size = sizeof(struct sockaddr_in);

    if (sendto(socket_desc,(void*)rh,nbytes,0,(struct sockaddr*)to,size)==-1)
        return -1;

    //Change state of (socket,peer) pair to SENT_SYN
    if (setsstate(sock_id,to,SENT_SYN)==-1)
        return -1;

    fprintf(stderr,"Sent SYN with seqno %d\r\n",ntohl(rh->seqno));

    //Register timeout for SYN
    smalloc(struct timeval,current_time);
    if (gettimeofday(current_time,NULL)<0)
    {
        perror("rudp_event_handler: gettimeofday");
        return -1;
    }
    current_time->tv_usec += RUDP_TIMEOUT*2000;

    //Create an argument for the event_timeout function
    state[sock_id].stap[pnum] = (struct timeout_arg*)malloc(sizeof(struct timeout_arg));
    state[sock_id].stap[pnum]->seqno = ntohl(rh->seqno);
    state[sock_id].stap[pnum]->sock_id = sock_id;
    state[sock_id].stap[pnum]->socket_desc = socket_desc;
    state[sock_id].syn_timed_out[pnum] = 0;

    state[sock_id].stap[pnum]->to = (struct sockaddr_in*)malloc(sizeof(struct sockaddr_in));
    memcpy(state[sock_id].stap[pnum]->to,to,sizeof(struct sockaddr_in*));
    state[sock_id].stap[pnum]->to->sin_addr = to->sin_addr;

    //Actually register the timeout handlet
    event_timeout(*current_time,udp_syn_timeoutf,(void*)(state[sock_id].stap[pnum]),"packet timeout");
    return 0;
}

int acknowledge(int socket_desc, int sock_id, struct sockaddr* to, u_int32_t seqno)
{
    int pnum = getpeernum(sock_id,to);
    
    if (seqno==state[sock_id].syn_seqno[pnum]+1)
    {
        event_timeout_delete(udp_syn_timeoutf,(void*)state[sock_id].stap[pnum]);
        state[sock_id].syn_timed_out[pnum] = 0;
        return 0;
    }

    if (seqno==state[sock_id].fin_seqno[pnum]+1)
    {
        event_timeout_delete(udp_fin_timeoutf,(void*)state[sock_id].ftap[pnum]);
        state[sock_id].fin_timed_out[pnum] = 0;
        return 0;
    }

    u_int32_t cnum = state[sock_id].curr[pnum]->tap->seqno;

    if (seqno<=(cnum+1))
    {
        //Move {shift} packets left
        int shift = state[sock_id].curr[pnum]->num - state[sock_id].first_packet[pnum];
        if (shift>0)
        {
            int i;
            for (i=0;i<shift;i++)
            {
                state[sock_id].curr[pnum] = state[sock_id].curr[pnum]->prev;
            }

            //Now, move {shift} times right and acknowledge each packet
            for (i=0;i<shift;i++)
            {
                state[sock_id].curr[pnum] = state[sock_id].curr[pnum]->next;
                event_timeout_delete(udp_timeoutf,state[sock_id].curr[pnum]->tap);
                if (state[sock_id].window_size[pnum]<3) state[sock_id].window_size[pnum]++;
                state[sock_id].curr[pnum]->timed_out = 0;
                if (i<(shift-1)) free(state[sock_id].curr[pnum]->prev);
            }
            //Update the number of the first packet
            state[sock_id].first_packet[pnum]+=shift;
        }
    }
    return 0;
}

int compare_peers(struct sockaddr_in peer1, struct sockaddr_in peer2)
{
    int flag = 1;
    if (peer1.sin_addr.s_addr!=peer2.sin_addr.s_addr)
        flag = 0;
    //if (peer1.sin_family!=peer2.sin_family)
    //    flag = 0;
    if (peer1.sin_port!=peer2.sin_port)
        flag = 0;
    return flag;
}

int getsstate(int sock_id,struct sockaddr_in* peer)
{
    int i;
    for (i=0; i<state[sock_id].num_of_peers;i++)
        if (compare_peers(*(state[sock_id].peers[i]),*peer)==1)
            return state[sock_id].peer_state[i];
    return INITIAL;
}

int setsstate(int sock_id,struct sockaddr_in* peer,int newstate)
{
    int i;
    for (i=0; i<=state[sock_id].num_of_peers;i++)
        if (compare_peers(*(state[sock_id].peers[i]),*peer)==1)
        {
            state[sock_id].peer_state[i] = newstate;
            return state[sock_id].peer_state[i];
        }
    return -1;
}

int addpeer(int sock_id, struct sockaddr_in* peer)
{
    state[sock_id].peers[state[sock_id].num_of_peers] = (struct sockaddr_in*)malloc(sizeof(struct sockaddr_in));
    memcpy(state[sock_id].peers[state[sock_id].num_of_peers++],peer,sizeof(struct sockaddr_in));
    return 1;
}

int getpeernum(int sock_id, struct sockaddr_in* peer)
{
    int i;
    for (i=0;i<=state[sock_id].num_of_peers;i++)
        if (compare_peers(*(state[sock_id].peers[i]),*peer)==1)
            return i;
}

int udp_recvf(int ia, void* va)
{
    //Convert the arguments back from void*
    struct rudp_socket_int* sockint = (struct rudp_socket_int*) va;

    //Allocate memory for receiving address and address length
    smalloc(struct sockaddr_in,recv_addr);
    socklen_t length_ptr = sizeof(struct sockaddr_in);

    //Read the data
    char* buf = (char*)malloc(10+RUDP_MAXPKTSIZE);
    int recv_bytes = recvfrom(ia,(void *)buf,10+RUDP_MAXPKTSIZE,0,(struct sockaddr_in*)recv_addr,&length_ptr);

    if (recv_bytes<0)
    {
        perror("udp_recvf: recvfrom failed");
        return -1;
    }

    //Get the RUDP header
    smalloc(struct rudp_hdr,rh);
    memcpy(rh,(char*)buf,sizeof(struct rudp_hdr));

    //Now, check out socket/peer state
    int cstate = getsstate(sid,(struct sockaddr_in*)recv_addr);
    switch(cstate)
    {
        case INITIAL:
        {
            if (ntohs(rh->version)==RUDP_VERSION)
            {
                u_int16_t ptype = ntohs(rh->type);
                switch (ptype)
                {
                    case RUDP_SYN:
                    {
                        fprintf(stderr,"Received a SYN on a socket in state INITIAL: seqno %d\r\n",ntohl(rh->seqno));
                        send_ack(sockint->socket_desc,sid,(struct sockaddr*)recv_addr,ntohl(rh->seqno)+1);
                        break;
                    }
                    case RUDP_FIN:
                    {
                        fprintf(stderr,"Received FIN on a socket in state INITIAL: seqno %d\r\n",ntohl(rh->seqno));
                        send_ack(sockint->socket_desc,sid,(struct sockaddr*)recv_addr,ntohl(rh->seqno)+1);
                        break;
                    }
                    case RUDP_DATA:
                    {
                        fprintf(stderr,"Received a DATA packet on a socket in state INITIAL\r\n");
                        send_ack(sockint->socket_desc,sid,(struct sockaddr*)recv_addr,ntohl(rh->seqno)+1);
                        
                        int recv_body_len = recv_bytes-sizeof(struct rudp_hdr)-16;
                        char* recv_body = (char*)malloc(recv_body_len);
                        memcpy(recv_body,buf+sizeof(struct rudp_hdr)+16,recv_body_len);

                        if ((state[sid].udp_recv_fh)((rudp_socket_t)ia,recv_addr,(void*)recv_body,recv_body_len)<0)
                            return -1;
                        break;
                    }
                    case RUDP_ACK:
                    {
                        fprintf(stderr,"Received an ACK on a socket in state INITIAL: seqno %d\r\n",ntohl(rh->seqno));
                        break;
                    }
                    default:
                    {
                        fprintf(stderr,"Unexpected RUDP packet type: %d\r\n",ptype);
                    }
                }
            }
            else
            {
                fprintf(stderr,"Unexpected RUDP version: %d\r\n",ntohs(rh->version));
            }
            break;
        }

        case SENT_SYN:
        {
            //If the version is correct and it's an ACK for our SYN
            if (ntohs(rh->version)==RUDP_VERSION)
            {
                int ptype = ntohs(rh->type);
                switch (ptype)
                {
                    case RUDP_ACK:
                    {
                        fprintf(stderr,"Received an ACK on a socket in state SENT_SYN: seqno %d\r\n",ntohl(rh->seqno));
                        int pnum = getpeernum(sid,(struct sockaddr_in*)(recv_addr));
                        acknowledge(sockint->socket_desc,sid,(struct sockaddr*)recv_addr,ntohl(rh->seqno));
                        if (ntohl(rh->seqno)==state[sid].syn_seqno[pnum]+1)
                        {
                            //Change state to SENDING
                            if (setsstate(sid,(struct sockaddr_in*)recv_addr,SENDING)==-1)
                            {
                                perror("recvf: setsstate");
                                return -1;
                            }

                            //Send as much as possible
                            send_data(sockint->socket_desc,sid,pnum,(struct sockaddr*)recv_addr);
                        }
                        break;
                    }
                    case RUDP_SYN:
                    {
                        fprintf(stderr,"Received a SYN on a socket in state SENT_SYN: seqno %d\r\n",ntohl(rh->seqno));
                        send_ack(sockint->socket_desc,sid,(struct sockaddr*)recv_addr,ntohl(rh->seqno));
                        break;
                    }
                    case RUDP_FIN:
                    {
                        fprintf(stderr,"Received a FIN on a socket in state SENT_SYN: seqno %d\r\n",ntohl(rh->seqno));
                        send_ack(sockint->socket_desc,sid,(struct sockaddr*)recv_addr,ntohl(rh->seqno));
                        break;
                    }
                    case RUDP_DATA:
                    {
                        fprintf(stderr,"Received a DATA packet on a socket in state SENT_SYN: seqno %d\r\n",ntohl(rh->seqno));
                        send_ack(sockint->socket_desc,sid,(struct sockaddr*)recv_addr,ntohl(rh->seqno));
                        break;
                    }
                    default:
                    {
                        fprintf(stderr,"Unexpected RUDP packet type: %d\r\n",ptype);
                    }
                }
                break;
            }
            else
            {
                fprintf(stderr,"Unexpected RUDP version: %d\r\n",ntohs(rh->version));
            }
            break;
        }

        case SENDING:
        {
            if (ntohs(rh->version)==RUDP_VERSION)
            {
                int ptype = ntohs(rh->type);
                switch (ptype)
                {
                    case RUDP_ACK:
                    {
                        fprintf(stderr,"Received an ACK on a socket in state SENDING: seqno %d\r\n",ntohl(rh->seqno));
                        //Delete acknowledged packets from the queue
                        acknowledge(sockint->socket_desc,sid,(struct sockaddr*)recv_addr,ntohl(rh->seqno));
                        int pnum = getpeernum(sid,(struct sockaddr_in*)(recv_addr));
                        send_data(sockint->socket_desc,sid,pnum,(struct sockaddr*)recv_addr);
                        break;
                    }
                    case RUDP_DATA:
                    {
                        fprintf(stderr,"Received a DATA packet on a socket in state SENDING\r\n");
                        send_ack(sockint->socket_desc,sid,(struct sockaddr*)recv_addr,ntohl(rh->seqno));
                        
                        int recv_body_len = recv_bytes-sizeof(struct rudp_hdr)-16;
                        char* recv_body = (char*)malloc(recv_body_len);
                        memcpy(recv_body,buf+sizeof(struct rudp_hdr)+16,recv_body_len);

                        if ((state[sid].udp_recv_fh)((rudp_socket_t)ia,recv_addr,(void*)recv_body,recv_body_len)<0)
                            return -1;
                        break;
                    }
                    case RUDP_SYN:
                    {
                        fprintf(stderr,"Received a SYN on a socket in state SENDING: seqno %d\r\n",ntohl(rh->seqno));
                        send_ack(sockint->socket_desc,sid,(struct sockaddr*)recv_addr,ntohl(rh->seqno));
                        break;
                    }
                    case RUDP_FIN:
                    {
                        fprintf(stderr,"Received a FIN on a socket in state SENDING: seqno %d\r\n",ntohl(rh->seqno));
                        send_ack(sockint->socket_desc,sid,(struct sockaddr*)recv_addr,ntohl(rh->seqno));
                        break;
                    }
                    default:
                    {
                        fprintf(stderr,"Unexpected RUDP packet type: %d\r\n",ptype);
                    }
                }
            }
            else
            {
                fprintf(stderr,"Unexpected RUDP version: %d\r\n",ntohs(rh->version));
            }
            break;
        }

        case SENT_FIN:
        {
            if (ntohs(rh->version)==RUDP_VERSION)
            {
                int ptype = ntohs(rh->type);
                switch (ptype)
                {
                    case RUDP_ACK:
                    {
                        fprintf(stderr,"Received an ACK on a socket in state SENT_FIN: seqno %d\r\n",ntohl(rh->seqno));
                        int pnum = getpeernum(sid,(struct sockaddr_in*)(recv_addr));

                        //Delete acknowledged packets from the queue
                        acknowledge(sockint->socket_desc,sid,(struct sockaddr*)recv_addr,ntohl(rh->seqno));

                        //If this was an ACK for our FIN, check if the socket needs to be closed and close if necessary
                        if (ntohl(rh->seqno)==state[sid].fin_seqno[pnum]+1)
                        {
                            fprintf(stderr,"Received ACK for FIN\r\n");
                            if (state[sid].need_to_close==1)
                            {
                                setsstate(sid,(struct sockaddr_in*)recv_addr,CLOSED);
                                state[sid].udp_event_fh((rudp_socket_t)sockint->socket_desc,RUDP_EVENT_CLOSED,(struct sockaddr_in*)recv_addr);
                            }
                            else
                            {
                                setsstate(sid,(struct sockaddr_in*)recv_addr,INITIAL);
                            }
                        }
                        break;
                    }
                    case RUDP_SYN:
                    {
                        fprintf(stderr,"Received a SYN on a socket in state SENT_FIN: seqno %d\r\n",ntohl(rh->seqno));
                        send_ack(sockint->socket_desc,sid,(struct sockaddr*)recv_addr,ntohl(rh->seqno));
                        if (setsstate(sid,(struct sockaddr_in*)(recv_addr),INITIAL)==-1)
                        {
                            perror("recvf: setsstate");
                            return -1;
                        }
                        break;
                    }
                    case RUDP_DATA:
                    {
                        break;
                    }
                    case RUDP_FIN:
                    {
                        break;
                    }
                    default:
                    {
                        fprintf(stderr,"Unexpected RUDP packet type: %d\r\n",ptype);
                    }
                }
            }
            else
            {
                fprintf(stderr,"Unexpected RUDP version: %d\r\n",ntohs(rh->version));
            }
            break;
        }
        case CLOSED:
        {
            return -1;
        }
    }
    return 0;
}

int stubf(int ia,void* va)
{
    return 0;
}

rudp_socket_t rudp_socket(int port)
{

    //Get the socket port number and check if it is valid
    srand(num_of_sockets+8);
    int socket_port = ((port == 0) ? (rand() % 1001 + 1000) : (port));
    if (socket_port<0)
        return NULL;

    smalloc(struct rudp_socket_int,res);

    //Create a socket and bind it to local port
    res->socket_desc = socket(PF_INET,SOCK_DGRAM,0);
    if (res->socket_desc==-1)
        return NULL;

    res->local_addr.sin_family = PF_INET;
    res->local_addr.sin_addr.s_addr = INADDR_ANY;
    res->local_addr.sin_port = htons(socket_port);
    res->socket_id = num_of_sockets;

    state[num_of_sockets].need_to_close = 0;
    state[num_of_sockets].num_of_peers = 0;
    state[num_of_sockets].window_size[state[num_of_sockets].num_of_peers] = 3;

    if (bind(res->socket_desc,(struct sockaddr *)&(res->local_addr),sizeof(res->local_addr)) == -1)
        return NULL;

    //Register a udp listening function for this socket
    event_fd(res->socket_desc,udp_recvf,(void*)res,"rudp_recvfrom_handler");
    state[res->socket_id].udp_recv_fh = stubf;
    state[res->socket_id].udp_event_fh = stubf;

    num_of_sockets++;

    fprintf(stderr,"\r\n%d %s:%d %d\r\n",res->socket_id,inet_ntoa(res->local_addr.sin_addr),ntohs(res->local_addr.sin_port),res->socket_desc);
    return (rudp_socket_t)res;
}

int rudp_close(rudp_socket_t rsocket)
{

    //Create an internal socket structure
    smalloc (struct rudp_socket_int,sockint);
    memcpy(sockint,rsocket,sizeof(struct rudp_socket_int));

    state[sid].need_to_close = 1;
    return 0;
}

int udp_eventf(int ia, void* va)
{
    return 0;
}

int rudp_recvfrom_handler(rudp_socket_t rsocket, 
			  int (*handler)(rudp_socket_t, struct sockaddr_in *, 
					 char *, int))
{

    fprintf(stderr,"rudp_recvfrom_handler called\r\n");
    smalloc(struct rudp_socket_int,sockint);
    memcpy(sockint,rsocket,sizeof(struct rudp_socket_int));

    //Store the handler
    state[sid].udp_recv_fh = handler;
    return 0;
}

int rudp_event_handler(rudp_socket_t rsocket, 
		       int (*handler)(rudp_socket_t, rudp_event_t, 
				      struct sockaddr_in *))
{

    fprintf(stderr,"rudp_event_handler called\r\n");
    smalloc(struct rudp_socket_int,sockint);
    memcpy(sockint,rsocket,sizeof(struct rudp_socket_int));
 
    //Store the handler
    state[sid].udp_event_fh = handler;
    return 0;
}

int rudp_sendto(rudp_socket_t rsocket, void* data, int len, struct sockaddr_in* to) {

    if (len>RUDP_MAXPKTSIZE)
    {
        perror("rudp_sendto: packet too long");
        return -1;
    }

    //Create an internal socket structure
    struct rudp_socket_int* sockint = (struct rudp_socket_int*)malloc(sizeof(struct rudp_socket_int));
    memcpy(sockint,rsocket,sizeof(struct rudp_socket_int));

    //Look up state of (socket,remote peer) pair
    int cstate = getsstate(sockint->socket_id,to);
    switch(cstate)
    {
        case INITIAL:
        {
            //Add a peer to the socket
            if (addpeer(sid,to)==-1) return -1;
            int pnum = getpeernum(sid,to);

            //Create a queue for this socket/peer pair and add the packet to this queue
            init_queue(sid,pnum);            
            add_packet(data,len,sid,pnum);

            //Send a SYN packet
            send_syn(sockint->socket_desc,sid,pnum,to);
            break;
        }

        case SENT_SYN:
        {
            int pnum = getpeernum(sid,to);
            //Get the data and add it to the queue
            add_packet(data,len,sid,pnum);

            break;
        }

        case SENDING:
        {
            int pnum = getpeernum(sid,to);
            //Get the data and add it to the queue
            add_packet(data,len,sid,pnum);
            send_data(sockint->socket_desc,sid,pnum,to);
            break;
        }

        case SENT_FIN:
        {
            int pnum = getpeernum(sid,to);
            //Get the data and add it to the queue
            add_packet(data,len,sid,pnum);
            setsstate(sid,to,SENDING);
            break;
        }

        case CLOSED:
        {
            return -1;
        }
    }
    return 0;
}
